/* Hello, Emacs, this is -*-C-*- */

/* GNUPLOT - dumb.trm */

/*[
 * Copyright 1991 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * This file is included by ../term.c.
 *
 * This terminal driver supports:
 *   DUMB terminals
 *
 * AUTHORS
 *   Francois Pinard, 91-04-03
 *           INTERNET: pinard@iro.umontreal.ca
 *
 *   Ethan A Merritt Nov 2003
 *	Added support for enhanced text mode.
 *	Yes, this is frivolous, but it serves as an example for
 *	adding enhanced text to other terminals.  You can disable
 *	it by adding a line
 *	#define NO_DUMB_ENHANCED_SUPPORT
 *
 *   Bastian Maerkisch Nov 2016
 *	ANSI color support.  Filled polygons.
 *
 *   Bastian Maerkisch Jul 2021
 *	Filled boxes.
 *	Bold/Italic escape sequences.
 *	(Partial) wide character support.
 *	Wide characters are problematic as they would destroy the layout.  We
 *	handle this by marking subsequent cells as "occupied" with NUL.  These
 *	cells do not get printed.  Subsequent calls to set_pixel() also blank
 *	the cell to the left, if the cell is marked as occupied, and the one to
 *	the right if they overwrite a wide character.
 *	Note that is_wide_char() uses the same incomplete logic as mbwidth in
 *	readline.c and strwidth_utf8() in estimate.trm.
 *
 *   Ethan A Merritt Dec 2021
 *	fillchar
 *
 * send your comments or suggestions to (gnuplot-info@lists.sourceforge.net).
 *
 */
#include "driver.h"

#ifdef TERM_REGISTER
register_term(dumb_driver)
#endif

#ifdef TERM_PROTO
TERM_PUBLIC void DUMB_options(void);
TERM_PUBLIC void DUMB_init(void);
TERM_PUBLIC void DUMB_graphics(void);
TERM_PUBLIC void DUMB_text(void);
TERM_PUBLIC void DUMB_reset(void);
TERM_PUBLIC void DUMB_linetype(int linetype);
TERM_PUBLIC void DUMB_move(unsigned int x, unsigned int y);
TERM_PUBLIC void DUMB_point(unsigned int x, unsigned int y, int point);
TERM_PUBLIC void DUMB_vector(unsigned int x, unsigned int y);
TERM_PUBLIC void DUMB_put_text(unsigned int x, unsigned int y, const char *str);
TERM_PUBLIC void DUMB_arrow(unsigned int sx, unsigned int sy,
			    unsigned int ex, unsigned int ey,
			    int head);
TERM_PUBLIC int DUMB_set_font(const char *s);

#ifndef NO_DUMB_ENHANCED_SUPPORT
/* To support "set term dumb enhanced" (don't ask why!) */
TERM_PUBLIC void ENHdumb_put_text(unsigned int x, unsigned int y, const char str[]);
TERM_PUBLIC void ENHdumb_OPEN(char * fontname, double fontsize,
	                      double base, TBOOLEAN widthflag, TBOOLEAN showflag,
			      int overprint);
TERM_PUBLIC void ENHdumb_FLUSH(void);
#else
#define ENHdumb_put_text NULL
#endif
#ifndef NO_DUMB_COLOR_SUPPORT
TERM_PUBLIC int DUMB_make_palette(t_sm_palette *palette);
TERM_PUBLIC void DUMB_set_color(t_colorspec *);
#endif
TERM_PUBLIC void DUMB_fillbox(int style, unsigned int x, unsigned int y,
			      unsigned int w, unsigned int h);
TERM_PUBLIC void DUMB_filled_polygon(int points, gpiPoint *corners);

#define DUMB_XMAX 79
#define DUMB_YMAX 24

#endif /* TERM_PROTO */

#ifdef TERM_BODY

#define DUMB_AXIS_CONST '\1'
#define DUMB_BORDER_CONST '\2'
#define DUMB_FILL_CONST '\3'
#define DUMB_NODRAW_CONST '\4'

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include "readline.h"

/* UTF-8 support */
typedef int32_t charcell;
/* text attribute support */
typedef struct _text_attr {
    unsigned char bold : 1;
    unsigned char italic : 1;
    unsigned char superscript : 1;
    unsigned char subscript : 1;
    unsigned char font : 4;
} text_attr;


/* matrix of characters */
static charcell *dumb_matrix = NULL;
#ifndef NO_DUMB_COLOR_SUPPORT
/* matrix of colors */
static t_colorspec *dumb_colors = NULL;
static t_colorspec dumb_color;
static t_colorspec dumb_prev_color;
#endif
static text_attr *dumb_attributes = NULL;
static text_attr dumb_attr;

/* current character used to draw */
static char dumb_pen;
/* current X position */
static int dumb_x;
/* current Y position */
static int dumb_y;
static int dumb_xmax = DUMB_XMAX;
static int dumb_ymax = DUMB_YMAX;
static TBOOLEAN dumb_feed = TRUE;
static int dumb_colormode = 0;
static TBOOLEAN dumb_text_attributes = FALSE;
static char *dumb_fillchar = NULL;

#define DUMB_PIXEL(x,y) dumb_matrix[dumb_xmax*(y)+(x)]

const char * dumb_attrstring(text_attr * attr, text_attr * prev_text_attr);
const char * ansi_colorstring(t_colorspec * color, t_colorspec * prev_color);

static void dumb_set_pixel(int x, int y, int v);
static unsigned nearest_ansi(rgb255_color rgb255);
static void dumb_rgb_color(rgb255_color rgb255, char * colorstring);

enum DUMB_id { DUMB_FEED, DUMB_NOFEED, DUMB_ENH, DUMB_NOENH,
               DUMB_SIZE, DUMB_ASPECT,
               DUMB_ANSI, DUMB_ANSI256, DUMB_ANSIRGB, DUMB_NOCOLOR,
               DUMB_ATTRIBUTES, DUMB_NOATTRIBUTES,
               DUMB_FILLCHAR, DUMB_OTHER };

static struct gen_table DUMB_opts[] =
{
    { "f$eed", DUMB_FEED },
    { "nof$eed", DUMB_NOFEED },
    { "enh$anced", DUMB_ENH },
    { "noe$nhanced", DUMB_NOENH },
    { "size", DUMB_SIZE },
    { "aspect", DUMB_ASPECT },
    { "ansi", DUMB_ANSI },
    { "ansi256", DUMB_ANSI256 },
    { "ansirgb", DUMB_ANSIRGB },
    { "attr$ibutes", DUMB_ATTRIBUTES },
    { "noattr$ibutes", DUMB_NOATTRIBUTES },
    { "mono", DUMB_NOCOLOR },
    { "fill$char", DUMB_FILLCHAR },
    { NULL, DUMB_OTHER }
};

TERM_PUBLIC void
DUMB_options()
{
    int x, y;
    int cmd;
    TBOOLEAN set_size = FALSE;

    while (!END_OF_COMMAND) {
	switch ((cmd = lookup_table(&DUMB_opts[0], c_token))) {
	case DUMB_FEED:
	    c_token++;
	    dumb_feed = TRUE;
	    break;
	case DUMB_NOFEED:
	    c_token++;
	    dumb_feed = FALSE;
	    break;
#ifndef NO_DUMB_ENHANCED_SUPPORT
	case DUMB_ENH:
	    c_token++;
	    term->put_text = ENHdumb_put_text;
	    term->flags |= TERM_ENHANCED_TEXT;
	    break;
	case DUMB_NOENH:
	    c_token++;
	    term->put_text = DUMB_put_text;
	    term->flags &= ~TERM_ENHANCED_TEXT;
	    break;
#endif
	case DUMB_ASPECT:
	    c_token++;
	    x = int_expression();
	    y = 1;
	    if (!END_OF_COMMAND && equals(c_token, ",")) {
		c_token++;
		y = int_expression();
	    }
	    if (x <= 0) x = 1;
	    if (y <= 0) y = 1;
	    term->h_tic = x;
	    term->v_tic = y;
 	    break;
	case DUMB_ANSI:
	case DUMB_ANSI256:
	case DUMB_ANSIRGB:
	    c_token++;
	    dumb_colormode = cmd;
#ifndef NO_DUMB_COLOR_SUPPORT
	    term->make_palette = DUMB_make_palette;
	    term->set_color = DUMB_set_color;
#endif
	    break;
	case DUMB_NOCOLOR:
	    c_token++;
	    dumb_colormode = 0;
	    term->make_palette = NULL;
	    term->set_color = null_set_color;
	    break;
	case DUMB_ATTRIBUTES:
	    c_token++;
	    dumb_text_attributes = TRUE;
	    break;
	case DUMB_NOATTRIBUTES:
	    c_token++;
	    dumb_text_attributes = FALSE;
	    break;
	case DUMB_FILLCHAR:
	    c_token++;
	    free(dumb_fillchar);
	    dumb_fillchar = NULL;
	    if (equals(c_token, "solid")) {
		c_token++;
		/* We mimic the way a user would have typed it in gnuplot syntax,
		 * not the C standard form "\u2588".
		 */
		dumb_fillchar = strdup("\\U+2588");
	    } else {
		dumb_fillchar = try_to_get_string();
	    }
	    if (dumb_fillchar)
		truncate_to_one_utf8_char(dumb_fillchar);
	    break;
	case DUMB_SIZE:
	    c_token++;
	    /* Fall through */
	case DUMB_OTHER:
	default:
	    if (set_size) {
		int_warn(c_token++,"unrecognized option");
		break;
	    }
	    x = int_expression();
	    if (x <= 0 || x > 1024)
		x = DUMB_XMAX;
	    if (!END_OF_COMMAND) {
		if (equals(c_token,","))
		    c_token++;
		y = int_expression();
		if (y <= 0 || y > 1024)
		    y = DUMB_YMAX;
		dumb_xmax = term->xmax = x;
		dumb_ymax = term->ymax = y;
	    }
	    set_size = TRUE;
 	    break;
	}
    }

    {
	const char * coloropts[] = {"mono", "ansi", "ansi256", "ansirgb"};
	char fillopt[24];

	sprintf(term_options, "%sfeed %s size %d, %d aspect %i, %i %s %sattributes",
	    dumb_feed ? "" : "no",
	    term->put_text == ENHdumb_put_text ? "enhanced" : "",
	    dumb_xmax, dumb_ymax,
	    term->h_tic, term->v_tic,
	    coloropts[dumb_colormode == 0 ? 0 : dumb_colormode - DUMB_ANSI + 1],
	    dumb_text_attributes ? "" : "no"
	    );
	if (dumb_fillchar && *dumb_fillchar) {
	    sprintf(fillopt, " fillchar \"%s\"", dumb_fillchar);
	    strcat(term_options, fillopt);
	}
    }
}


static TBOOLEAN
is_wide_char(unsigned char *charpixel)
{
    /* see also mbwidth() in readline.c */
    if (encoding == S_ENC_UTF8)
	return (*charpixel >= 0xe3);
    else
	return FALSE;
}


static void
dumb_set_pixel(int x, int y, int v)
{
    unsigned char *charpixel;

    if ((unsigned int) x <= dumb_xmax 	/* ie x>=0 && x<=dumb_xmax */
    &&  (unsigned int) y <= dumb_ymax) {
	charpixel = (unsigned char *)(&DUMB_PIXEL(x, y));
	if (*charpixel == NUL && (x > 0)) {
	    /* Also clear the wide character to the left */
	    unsigned char * c = (unsigned char *) &(DUMB_PIXEL((x - 1), y));
	    DUMB_PIXEL((x - 1), y) = 0;
	    *c = ' ';
	} else if (is_wide_char(charpixel) && ((x + 1) <= dumb_xmax)) {
	    /* This is a wide character, clear the cell to the right. */
	    char * c = (char *) &(DUMB_PIXEL((x + 1), y));
	    DUMB_PIXEL((x + 1), y) = 0;
	    *c = ' ';
	}
	/* null-terminate single ASCII character (needed for UTF-8) */
	DUMB_PIXEL(x, y) = 0;
	*charpixel = v;
#ifndef NO_DUMB_COLOR_SUPPORT
	memcpy(&dumb_colors[dumb_xmax * y + x], &dumb_color, sizeof(t_colorspec));
#endif
	if (dumb_text_attributes)
	    memset(&dumb_attributes[dumb_xmax * y + x], 0, sizeof(text_attr));
    }
}


TERM_PUBLIC void
DUMB_init()
{
    int size = (dumb_xmax+1) * (dumb_ymax+1);

    dumb_matrix = gp_realloc(dumb_matrix, size*sizeof(charcell), "dumb terminal");
#ifndef NO_DUMB_COLOR_SUPPORT
    dumb_colors = gp_realloc(dumb_colors, size*sizeof(t_colorspec), "dumb terminal");
#endif
    if (dumb_text_attributes)
	dumb_attributes = gp_realloc(dumb_attributes, size*sizeof(text_attr), "dumb terminal");
}


TERM_PUBLIC void
DUMB_graphics()
{
    int i;
    int size = (dumb_xmax+1) * (dumb_ymax+1);
    charcell *pm = dumb_matrix;

    memset(dumb_matrix, 0, size * sizeof(charcell));
#ifndef NO_DUMB_COLOR_SUPPORT
    memset(dumb_colors, 0, size * sizeof(t_colorspec));
#endif
    if (dumb_text_attributes) {
	memset(dumb_attributes, 0, size * sizeof(text_attr));
	memset(&dumb_attr, 0, sizeof(text_attr));
    }

    for (i = 0; i < size; i++) {
	char *c = (char *)pm++;
	*c = ' ';
    }
}


#ifndef NO_DUMB_COLOR_SUPPORT
/* code snippet adopted from libcaca:  WTFPL license */
/* RGB colours for the ANSI palette. There is no real standard, so we
 * use the same values as gnome-terminal. The 7th colour (brown) is a bit
 * special: 0xfa50 instead of 0xfaa0. */
static const unsigned ansitab16[16] =
{
    0xf000, 0xf00a, 0xf0a0, 0xf0aa, 0xfa00, 0xfa0a, 0xfa50, 0xfaaa,
    0xf555, 0xf55f, 0xf5f5, 0xf5ff, 0xff55, 0xff5f, 0xfff5, 0xffff,
};

static unsigned
nearest_ansi(rgb255_color rgb255)
{
    unsigned int i, best, dist;

    best = 0;
    dist = 0x3fff;
    for (i = 0; i < 16; i++) {
	unsigned int d = 0;
	int a, b;

	a = (ansitab16[i] >> 0) & 0xf;
	b = (rgb255.r >> 4) & 0xf;
	d += (a - b) * (a - b);

	a = (ansitab16[i] >> 4) & 0xf;
	b = (rgb255.g >> 4) & 0xf;
	d += (a - b) * (a - b);

	a = (ansitab16[i] >> 8) & 0xf;
	b = (rgb255.b >> 4) & 0xf;
	d += (a - b) * (a - b);

	if (d < dist) {
	    dist = d;
	    best = i;
	}
    }
    return best;
}
/* end of libcaca code */


static unsigned
to_ansi256(rgb255_color *c)
{
    if ((c->r - 8) / 10 == (c->b - 8) / 10 && (c->r - 8) / 10 == (c->g - 8) / 10) {
	/* gray scale */
	if (c->g < 8) /* black */
	    return 16;
	if (c->g >= 238) /* white */
	    return 231;
	return (c->g - 8) / 10 + 232;  /* like XTerm, Mintty */
    } else {
	/* 6x6x6 color cube */
#define RMAPCUBE6(n) ((n >= 55) ? ((n) - 35) / 40 : 0)
	return (((unsigned) RMAPCUBE6(c->r) * 36) +
	        ((unsigned) RMAPCUBE6(c->g) *  6) +
	        ((unsigned) RMAPCUBE6(c->b))) + 16;
    }
}


static void
dumb_rgb_color(rgb255_color rgb255, char * colorstring)
{
    switch (dumb_colormode) {
    case DUMB_ANSI: {
	unsigned color = nearest_ansi(rgb255);
	sprintf(colorstring, "\033[%i;%im", color >= 8 ? 1 : 22, 30 + (color % 8));
	break;
    }
    case DUMB_ANSI256:
	sprintf(colorstring, "\033[38;5;%im", to_ansi256(&rgb255));
	break;
    case DUMB_ANSIRGB:
	sprintf(colorstring, "\033[38;2;%i;%i;%im", rgb255.r, rgb255.g, rgb255.b);
	break;
    }
}


const char *
ansi_colorstring(t_colorspec * color, t_colorspec * prev_color)
{
    static char colorstring[256];

    colorstring[0] = NUL;
    switch (color->type) {
    case TC_LT: {
	int n;

	if (dumb_colormode < DUMB_ANSI)
	    break;
	if (prev_color != NULL && prev_color->type == TC_LT && prev_color->lt == color->lt)
	    break;
	n = color->lt + 1;
	/* map line type to colors */
	if (n <= 0) {
#if defined(OS2) || defined(MSDOS)
	    sprintf(colorstring, "\033[22;37m");  /* foreground color white */
#else
	    sprintf(colorstring, "\033[22;39m");  /* normal foreground color */
#endif
	} else {
	    if (n > 15) n = ((n - 1) % 15) + 1;
	    sprintf(colorstring, "\033[%i;%im", n >= 8 ? 1 : 22, 30 + (n % 8));
	}
	break;
    }
    case TC_FRAC: {
	rgb255_color rgb255;

	if (prev_color != NULL && prev_color->type == TC_FRAC && prev_color->value == color->value)
	    break;
	rgb255maxcolors_from_gray(color->value, &rgb255);
	dumb_rgb_color(rgb255, colorstring);
	break;
    }
    case TC_RGB: {
	rgb255_color rgb255;

	if (prev_color != NULL && prev_color->type == TC_RGB && prev_color->lt == color->lt)
	    break;
	rgb255.r = (color->lt >> 16) & 0xff;
	rgb255.g = (color->lt >>  8) & 0xff;
	rgb255.b = (color->lt >>  0) & 0xff;
	dumb_rgb_color(rgb255, colorstring);
	break;
    }
    default:
	break;
    }
    return colorstring;
}
#endif


const char *
dumb_attrstring(text_attr * attr, text_attr * prev_text_attr)
{
    static char attrstr[64];

    attrstr[0] = NUL;
    if ((attr->bold != prev_text_attr->bold) && (attr->italic != prev_text_attr->italic))
	sprintf(attrstr, "\033[%i;%im", attr->bold ? 1 : 22, attr->italic ? 3 : 23);
    else if (attr->bold != prev_text_attr->bold)
	sprintf(attrstr, "\033[%im", attr->bold ? 1 : 22);
    else if (attr->italic != prev_text_attr->italic)
	sprintf(attrstr, "\033[%im", attr->italic ? 3 : 23);
    return attrstr;
}


TERM_PUBLIC void
DUMB_text()
{
    int x, y, i;
    text_attr prev_text_attr;

    putc('\f', gpoutfile);

#ifndef NO_DUMB_COLOR_SUPPORT
    if (dumb_colormode > 0) {
	fputs("\033[0;39m", gpoutfile); /* reset colors to default */
	memset(&dumb_prev_color, 0, sizeof(t_colorspec));
    }
#endif

    if (dumb_text_attributes) {
	fputs("\033[22;23m", gpoutfile); /* reset attributes */
	memset(&prev_text_attr, 0, sizeof(text_attr));
    }

    for (y = dumb_ymax - 1; y >= 0; y--) {
	for (x = 0; x < dumb_xmax; x++) {
	    char *c;

#ifndef NO_DUMB_COLOR_SUPPORT
	    t_colorspec * color = &dumb_colors[dumb_xmax*y + x];
	    const char * colorstring = ansi_colorstring(color, &dumb_prev_color);
	    if (colorstring[0] != NUL) {
		fputs(colorstring, gpoutfile);
		memcpy(&dumb_prev_color, color, sizeof(t_colorspec));
	    }
#endif

	    c = (char *)(&DUMB_PIXEL(x, y));
	    if (*c != NUL) { /* Do not print cells occupied by wide characters. */
		/* character attributes */
		if (dumb_text_attributes) {
		    text_attr * attr = &dumb_attributes[dumb_xmax*y + x];
		    const char * attrstring = dumb_attrstring(attr, &prev_text_attr);
		    if (attrstring[0] != NUL) {
			fputs(attrstring, gpoutfile);
			memcpy(&prev_text_attr, attr, sizeof(text_attr));
		    }
		}

		/* The UTF-8 character might be four bytes long and so there's
		   no guarantee that the charcell ends in a NUL. */
		for (i = 0; i < sizeof(charcell) && *c != NUL; i++, c++)
		    fputc(*c, gpoutfile);
	    }
	}
	if (dumb_feed || y > 0)
	    putc('\n', gpoutfile);
    }
#ifndef NO_DUMB_COLOR_SUPPORT
    if (dumb_text_attributes)
	fputs("\033[22;23m", gpoutfile); /* reset attributes */
    if (dumb_colormode > 0)
	fputs("\033[0;39;49m", gpoutfile); /* reset colors to default */
#endif
    fflush(gpoutfile);
}


TERM_PUBLIC void
DUMB_reset()
{
    free(dumb_matrix);
    dumb_matrix = NULL;
#ifndef NO_DUMB_COLOR_SUPPORT
    free(dumb_colors);
    dumb_colors = NULL;
#endif
    free(dumb_attributes);
    dumb_attributes = NULL;
}


TERM_PUBLIC void
DUMB_linetype(int linetype)
{
    static char pen_type[7] = { '*', '#', '$', '%', '@', '&', '=' };

    if (linetype == LT_BLACK)
	dumb_pen = DUMB_BORDER_CONST;
    else if (linetype == LT_AXIS)
	dumb_pen = DUMB_AXIS_CONST;
    else if (linetype == LT_NODRAW)
	dumb_pen = DUMB_NODRAW_CONST;
    else if (linetype <= LT_NODRAW)
	dumb_pen = ' ';
    else {
	linetype = linetype % 7;
	dumb_pen = pen_type[linetype];
    }

#ifndef NO_DUMB_COLOR_SUPPORT
    dumb_color.type = TC_LT;
    dumb_color.lt = linetype;
#endif
}


TERM_PUBLIC void
DUMB_move(unsigned int x, unsigned int y)
{
    dumb_x = x;
    dumb_y = y;
}


TERM_PUBLIC void
DUMB_point(unsigned int x, unsigned int y, int point)
{
    dumb_set_pixel(x, y, point == -1 ? '.' : point % 26 + 'A');
}


TERM_PUBLIC void
DUMB_vector(unsigned int arg_x, unsigned int arg_y)
{
    int x = arg_x;		/* we need signed int, since
				 * unsigned-signed=unsigned and */
    int y = arg_y;		/* abs and cast to double wouldn't work */
    char pen, pen1;
    int delta;

    if (dumb_pen == DUMB_NODRAW_CONST) {
	DUMB_move(x, y);
	return;
    }

    if (ABS(y - dumb_y) > ABS(x - dumb_x)) {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:
	    pen = ':';
	    pen1 = '+';
	    break;

	case DUMB_BORDER_CONST:
	    pen = '|';
	    pen1 = '+';
	    break;

	case DUMB_FILL_CONST:
	    pen = pen1 = 'X';
	    break;

	default:
	    pen = dumb_pen;
	    pen1 = dumb_pen;
	    break;
	}
	dumb_set_pixel(dumb_x, dumb_y, pen1);
	for (delta = 1; delta < ABS(y - dumb_y); delta++) {
	    dumb_set_pixel(
		dumb_x  + (int)round((double) (x - dumb_x) * delta / ABS(y - dumb_y)),
		dumb_y + delta * sign(y - dumb_y), pen);
	}
	dumb_set_pixel(x, y, pen1);
    } else if (ABS(x - dumb_x) > ABS(y - dumb_y)) {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:
	    pen = '.';
	    pen1 = '+';
	    break;

	case DUMB_BORDER_CONST:
	    pen = '-';
	    pen1 = '+';
	    break;

	case DUMB_FILL_CONST:
	    pen = pen1 = 'X';
	    break;

	default:
	    pen = dumb_pen;
	    pen1 = dumb_pen;
	    break;
	}
	dumb_set_pixel(dumb_x, dumb_y, pen1);
	for (delta = 1; delta < ABS(x - dumb_x); delta++)
	    dumb_set_pixel(dumb_x + delta * sign(x - dumb_x),
			   dumb_y + (int)round((double) (y - dumb_y) * delta / ABS(x - dumb_x)),
			   pen);
	dumb_set_pixel(x, y, pen1);
    } else {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:	/* zero length axis */
	    pen = '+';
	    break;

	case DUMB_BORDER_CONST:	/* zero length border */
	    pen = '+';
	    break;

	case DUMB_FILL_CONST:
	    pen = '#';
	    break;

	default:
	    pen = dumb_pen;
	    break;
	}
	for (delta = 0; delta <= ABS(x - dumb_x); delta++)
	    dumb_set_pixel(dumb_x + delta * sign(x - dumb_x),
			   dumb_y + delta * sign(y - dumb_y),
			   pen);
    }
    dumb_x = x;
    dumb_y = y;
}


static void
utf8_copy_one(char *dest, const char *orig)
{
    const char *nextchar = orig;
    unsigned long wch;
    *((charcell *)dest) = 0;	/* zero-fill */

    if (encoding != S_ENC_UTF8) {
	*dest = *orig;
	return;
    }

    /* Valid UTF8 byte sequence */
    if (utf8toulong(&wch, &nextchar)) {
	while (orig < nextchar)
	    *dest++ = *orig++;
    } else {
	int_warn(NO_CARET, "invalid UTF-8 byte sequence");
	*dest++ = *orig++;
    }
}


TERM_PUBLIC void
DUMB_put_text(unsigned int x, unsigned int y, const char *str)
{
    int i, length;

    if (y > dumb_ymax)
	return;

    length = gp_strlen(str);
    if (x + length > dumb_xmax)
	x = GPMAX(0, dumb_xmax - length);

    for (i = 0; i < length && x < dumb_xmax; i++, x++) {
	utf8_copy_one((char *)(&DUMB_PIXEL(x, y)), gp_strchrn(str, i));
#ifndef NO_DUMB_COLOR_SUPPORT
	memcpy(&dumb_colors[dumb_xmax * y + x], &dumb_color, sizeof(t_colorspec));
#endif
	if (dumb_text_attributes)
	    dumb_attributes[dumb_xmax * y + x] = dumb_attr;
	if (is_wide_char((unsigned char *)(&DUMB_PIXEL(x, y))) && (x + 1) < dumb_xmax) {
	    DUMB_PIXEL(x + 1, y) = 0;  /* mark as occupied */
	    x++;
	}
    }
}


TERM_PUBLIC void
DUMB_arrow(
    unsigned int usx, unsigned int usy,
    unsigned int uex, unsigned int uey,
    int head)	/* mostly ignored */
{
    /* we have GOT to ditch this unsigned coord madness! */
    int sx = (int)(usx);
    int sy = (int)(usy);
    int ex = (int)(uex);
    int ey = (int)(uey);

    char saved_pen;
    char saved_x;
    char saved_y;

    saved_pen = dumb_pen;
    saved_x = dumb_x;
    saved_y = dumb_y;

    /* Arrow shaft */
    if (ex == sx) dumb_pen = '|';
    else if (ey == sy) dumb_pen = '-';
    else dumb_pen = '.';
    dumb_x = sx;
    dumb_y = sy;
    if (!(head & HEADS_ONLY))
	DUMB_vector(ex, ey);

    /* Arrow tail */
    if ((head & BACKHEAD)) {
	char tailsym;
	if (ex > sx) tailsym = '<';
	else if (ex < sx) tailsym = '>';
	else if (ey > sy) tailsym = 'v';
	else tailsym = '^';
	dumb_set_pixel(sx, sy, tailsym);
    }

    /* Arrow head */
    if ((head & END_HEAD)) {
	char headsym;
	if (ex > sx) headsym = '>';
	else if (ex < sx) headsym = '<';
	else if (ey > sy) headsym = '^';
	else headsym = 'v';
	dumb_set_pixel(ex, ey, headsym);
    }

    dumb_pen = saved_pen;
    dumb_x = saved_x;
    dumb_y = saved_y;
}


#ifndef NO_DUMB_ENHANCED_SUPPORT
/*
 * The code from here on serves as an example of how to
 * add enhanced text mode support to even a dumb driver.
 */

static TBOOLEAN ENHdumb_opened_string;
static TBOOLEAN ENHdumb_show = TRUE;
static int ENHdumb_overprint = 0;
static TBOOLEAN ENHdumb_widthflag = TRUE;
static unsigned int ENHdumb_xsave, ENHdumb_ysave;
#define ENHdumb_fontsize 1
#define ENHdumb_font ""
static double ENHdumb_base;

TERM_PUBLIC void
ENHdumb_OPEN(
    char *fontname,
    double fontsize, double base,
    TBOOLEAN widthflag, TBOOLEAN showflag,
    int overprint)
{
    /* There are two special cases:
     * overprint = 3 means save current position
     * overprint = 4 means restore saved position
     */
    if (overprint == 3) {
	ENHdumb_xsave = dumb_x;
	ENHdumb_ysave = dumb_y;
	return;
    } else if (overprint == 4) {
	DUMB_move(ENHdumb_xsave, ENHdumb_ysave);
	return;
    }

    if (!ENHdumb_opened_string) {
	ENHdumb_opened_string = TRUE;
	/* Start new text fragment */
	    enhanced_cur_text = &enhanced_text[0];
	/* Scale fractional font height to vertical units of display */
	    ENHdumb_base = base * 2 / fontsize;
	/* Keep track of whether we are supposed to show this string */
	    ENHdumb_show = showflag;
	/* 0/1/2  no overprint / 1st pass / 2nd pass */
	    ENHdumb_overprint = overprint;
	/* widthflag FALSE means do not update text position after printing */
	    ENHdumb_widthflag = widthflag;
	/* Many drivers will need to do something about font selection here */
	    DUMB_set_font(fontname);  /* we ignore the font size */
    }
}

TERM_PUBLIC void
ENHdumb_FLUSH()
{
    char *str = enhanced_text;	/* The fragment to print */
    int x = dumb_x;		/* The current position  */
    int y = dumb_y;
    int i, len;

    if (ENHdumb_opened_string) {
	*enhanced_cur_text = '\0';

	len = gp_strlen(str);

	/* NB: base expresses offset from current y pos */
	i = ENHdumb_base;
	cliptorange(i, -1, 1);
	y += i;

	/* print the string fragment, perhaps invisibly */
	if (ENHdumb_show && y < dumb_ymax) {
	    for (i = 0; i < len && x < dumb_xmax; i++, x++) {
		utf8_copy_one( (char *)(&DUMB_PIXEL(x, y)), gp_strchrn(str,i));
#ifndef NO_DUMB_COLOR_SUPPORT
		memcpy(&dumb_colors[dumb_xmax * y + x], &dumb_color, sizeof(t_colorspec));
#endif
		if (dumb_text_attributes)
		    dumb_attributes[dumb_xmax * y + x] = dumb_attr;
	    }
	}

	if (!ENHdumb_widthflag)
	    /* don't update position */
	    ;
	else if (ENHdumb_overprint == 1)
	    /* First pass of overprint, leave position in center of fragment */
	    dumb_x += len / 2;
	else
	    /* Normal case is to update position to end of fragment */
	    dumb_x += len;

	ENHdumb_opened_string = FALSE;
    }
}

TERM_PUBLIC void
ENHdumb_put_text(unsigned int x, unsigned int y, const char *str)
{
    int length;

    /* If no enhanced text processing is needed, we can use the plain  */
    /* vanilla put_text() routine instead of this fancy recursive one. */
    if (ignore_enhanced_text || (!strpbrk(str, "{}^_@&~") && !contains_unicode(str))) {
	DUMB_put_text(x, y, str);
	return;
    }

    length = estimate_strlen(str, NULL);
    if (x + length > dumb_xmax)
	x = GPMAX(0, dumb_xmax - length);
    if (y > dumb_ymax)
	return;

    /* Set up global variables needed by enhanced_recursion() */
    enhanced_fontscale = 1.0;
    ENHdumb_opened_string = FALSE;
    strncpy(enhanced_escape_format, "%c", sizeof(enhanced_escape_format));

    DUMB_move(x,y);

    /* Set the recursion going. We say to keep going until a
     * closing brace, but we don't really expect to find one.
     * If the return value is not the nul-terminator of the
     * string, that can only mean that we did find an unmatched
     * closing brace in the string. We increment past it (else
     * we get stuck in an infinite loop) and try again.
     */
    while (*(str = enhanced_recursion((char *)str, TRUE,
    			ENHdumb_font, ENHdumb_fontsize,
			0.0, TRUE, TRUE, 0))) {
	(term->enhanced_flush)();

	/* I think we can only get here if *str == '}' */
	    enh_err_check(str);

	if (!*++str)
	    break; /* end of string */

	/* else carry on and process the rest of the string */
    }

    DUMB_set_font("");
}
#endif /* NO_DUMB_ENHANCED_SUPPORT */


TERM_PUBLIC int
DUMB_set_font(const char *s)
{
    /* reset current attributes */
    if (dumb_text_attributes)
	memset(&dumb_attr, 0, sizeof(text_attr));

    if (s == NULL || *s == NUL || !dumb_text_attributes)
	return TRUE;

    /* test for attributes */
    if ((strstr(s, ":Bold") != NULL))
	dumb_attr.bold = 1;
    if ((strstr(s, ":Italic") != NULL))
	dumb_attr.italic = 1;

    return TRUE;
}


#ifndef NO_DUMB_COLOR_SUPPORT
TERM_PUBLIC int
DUMB_make_palette(t_sm_palette *palette)
{
    /* report continuous colors */
    return 0;
}


TERM_PUBLIC void
DUMB_set_color(t_colorspec *colorspec)
{
    memcpy(&dumb_color, colorspec, sizeof(t_colorspec));
}
#endif


TERM_PUBLIC void
DUMB_fillbox(int style, unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
    unsigned int nx, ny;
    char *fillchar = (dumb_fillchar) ? dumb_fillchar : "#";

    for (ny = y; ny < y + GPMAX(h, 1); ny++) {
	for(nx = x; nx <= (x + GPMAX(w, 1) - 1); nx++)
	    DUMB_put_text(nx, ny, fillchar);
    }
}


static int
dumb_float_compare(const void * elem1, const void * elem2)
{
    int val = *(float *)elem1 - *(float *)elem2;
    return (0 < val) - (val < 0);
}


/* adopted copy from caca.trm */
TERM_PUBLIC void
DUMB_filled_polygon(int points, gpiPoint *corners)
{
    /* Eliminate duplicate polygon points. */
    if ((corners[0].x == corners[points - 1].x) && (corners[0].y == corners[points - 1].y))
	points--;
    /* Need at least three remaining points */
    if (points < 3)
	return;

    {
	/* ----------------------------------------------------------------
	 * Derived from
	 *  public-domain code by Darel Rex Finley, 2007
	 *  http://alienryderflex.com/polygon_fill/
	 * ---------------------------------------------------------------- */
	int nodes;
	float * nodeX;
	int pixelY;
	int i, j;
	int ymin = dumb_ymax, ymax = 0;
	int xmin = dumb_xmax, xmax = 0;
	char *fillchar = (dumb_fillchar) ? dumb_fillchar : "#";

	/* Find bounding box */
	for (i = 0; i < points; i++) {
	     if (corners[i].x < xmin) xmin = corners[i].x;
	     if (corners[i].x > xmax) xmax = corners[i].x;
	     if (corners[i].y < ymin) ymin = corners[i].y;
	     if (corners[i].y > ymax) ymax = corners[i].y;
	}

	/* Dynamically allocate node list. */
	nodeX = (float *) gp_alloc(sizeof(* nodeX) * points, "nodeX");

	/* Loop through the rows of the image. */
	for (pixelY = ymin; pixelY <= ymax + 1; pixelY++) {
	    /* Build a sorted list of nodes. */
	    nodes = 0;
	    j = points - 1;
	    for (i = 0; i < points; i++) {
		if (((corners[i].y < pixelY) && (corners[j].y >= pixelY)) ||
		    ((corners[j].y < pixelY) && (corners[i].y >= pixelY))) {
			nodeX[nodes++] = (corners[i].x +
			                  + (double) (pixelY - corners[i].y)
			                  / (double) (corners[j].y - corners[i].y)
			                  * (double) (corners[j].x - corners[i].x));
		}
		j = i;
	    }
	    qsort(nodeX, nodes, sizeof(float), dumb_float_compare);

	   /* Fill the pixels between node pairs. */
	   for (i = 0; i < nodes; i += 2) {
		unsigned int nx;
		if (nodeX[i] > xmax)
		    break;
		if (nodeX[i + 1] >= 0) {
		    /* TODO: Are these checks ever required? */
		     if (nodeX[i] < xmin)
			nodeX[i] = xmin;
		    if (nodeX[i + 1] > xmax)
			nodeX[i + 1] = xmax;
		    /* skip lines with zero length */
		    if (nodeX[i + 1] - nodeX[i] < 0.5)
			continue;
		    for (nx = (int)(nodeX[i] + 0.5); nx <= (int)(nodeX[i + 1]); nx++)
			DUMB_put_text(nx, pixelY, fillchar);
		}
	    }
	}

	/* cleanup */
	free(nodeX);
	/* ---------------------------------------------------------------- */
    }
}

#endif /* TERM_BODY */

#ifdef TERM_TABLE
TERM_TABLE_START(dumb_driver)
    "dumb", "ascii art for anything that prints text",
    DUMB_XMAX, DUMB_YMAX, 1, 1,
    1, 2, /* account for typical aspect ratio of characters */
    DUMB_options, DUMB_init, DUMB_reset,
    DUMB_text, null_scale, DUMB_graphics, DUMB_move, DUMB_vector,
    DUMB_linetype,
#ifndef NO_DUMB_ENHANCED_SUPPORT
    ENHdumb_put_text,
#else
    DUMB_put_text,
#endif
    null_text_angle,
    null_justify_text, DUMB_point, DUMB_arrow, DUMB_set_font,
    NULL,				/* pointsize */
#ifndef NO_DUMB_ENHANCED_SUPPORT
    TERM_CAN_MULTIPLOT | TERM_ENHANCED_TEXT,
#else
    TERM_CAN_MULTIPLOT,
#endif
    NULL, NULL, DUMB_fillbox, NULL,
#ifdef USE_MOUSE
    NULL, NULL, NULL, NULL, NULL,
#endif
    NULL,	/* Color support sets this to DUMB_make_palette */
    NULL, 	/* previous_palette */
    NULL,	/* Color support sets this to DUMB_set_color */
    DUMB_filled_polygon,
    NULL,	/* image */
#ifndef NO_DUMB_ENHANCED_SUPPORT
    ENHdumb_OPEN, ENHdumb_FLUSH, do_enh_writec
#endif /* NO_DUMB_ENHANCED_SUPPORT */
TERM_TABLE_END(dumb_driver)

#undef LAST_TERM
#define LAST_TERM dumb_driver

#endif /* TERM_TABLE */

#ifdef TERM_HELP
START_HELP(dumb)
"1 dumb",
"?commands set terminal dumb",
"?set terminal dumb",
"?set term dumb",
"?terminal dumb",
"?term dumb",
"?dumb",
" The `dumb` terminal driver plots into a text block using ascii characters.",
" It has an optional size specification and a trailing linefeed flag.",
"",
" Syntax:",
"       set terminal dumb {size <xchars>,<ychars>} {[no]feed}",
"                         {aspect <htic>{,<vtic>}}",
#ifndef NO_DUMB_ENHANCED_SUPPORT
"                         {[no]enhanced}",
#endif
"                         {fillchar {solid|\"<char>\"}}",
"                         {[no]attributes}",
#ifndef NO_DUMB_COLOR_SUPPORT
"                         {mono|ansi|ansi256|ansirgb}",
#endif
"",
" where <xchars> and <ychars> set the size of the text block. The default is",
" 79 by 24. The last newline is printed only if `feed` is enabled.",
"",
" The `aspect` option can be used to control the aspect ratio of the plot by",
" setting the length of the horizontal and vertical tic marks. Only integer",
" values are allowed. Default is 2,1 -- corresponding to the aspect ratio of",
" common screen fonts.",
"",
" The character \"#\" is used for area-fill. You can replace this with any",
" character available in the terminal font.  `fillchar solid` is short for",
" `fillchar \"\\U+2588\"` (unicode FULL BLOCK).",
"",
#ifndef NO_DUMB_COLOR_SUPPORT
" The `ansi`, `ansi256`, and `ansirgb` options will include escape",
" sequences in the output to handle colors.  Note that these might",
" not be handled by your terminal.  Default is `mono`.",
" To obtain the best color match in `ansi` mode, you should use",
" `set colorsequence classic`.",
" Depending on the mode, the `dumb` terminal will emit the",
" following sequences (without the additional whitespace):",
"",
"       ESC [ 0 m           reset attributes to defaults",
"       foreground color:",
"       ESC [ 1 m           set intense/bold",
"       ESC [ 22 m          intense/bold off",
"       ESC [ <fg> m        with color code 30 <= <fg> <= 37",
"       ESC [ 39 m          reset to default",
"       ESC [ 38; 5; <c> m  with palette index 16 <= <c> <= 255",
"       ESC [ 38; 2; <r>; <g>; <b> m  with components 0 <= <r,g,b> <= 255",
"       background color:",
"       ESC [ <bg> m        with color code 40 <= <bg> <= 47",
"       ESC [ 49 m          reset to default",
"       ESC [ 48; 5; <c> m  with palette index 16 <= <c> <= 231",
"       ESC [ 48; 2; <r>; <g>; <b> m  with components 0 <= <r,g,b> <= 255",
"",
" See also e.g. the description at",
"^ <a href=\"https://en.wikipedia.org/wiki/ANSI_escape_code#Colors\">",
"           https://en.wikipedia.org/wiki/ANSI_escape_code#Colors",
"^ </a>",
"",
#endif
"",
" The `attributes` option enables bold and italic text on terminals or",
" emulators that support the escape sequences",
"       ESC [ 1 m / 22 m    for bold on/off and",
"       ESC [ 3 m / 23 m    for italic on /off.",
"",
" Example:",
"       set term dumb mono size 60,15 aspect 1",
"       set tics nomirror scale 0.5",
"       plot [-5:6.5] sin(x) with impulse ls -1",
"",
"           1 +-------------------------------------------------+",
"         0.8 +|||++                   ++||||++                 |",
"         0.6 +|||||+                 ++|||||||+  sin(x) +----+ |",
"         0.4 +||||||+               ++|||||||||+               |",
"         0.2 +|||||||+             ++|||||||||||+             +|",
"           0 ++++++++++++++++++++++++++++++++++++++++++++++++++|",
"        -0.2 +        +|||||||||||+              +|||||||||||+ |",
"        -0.4 +         +|||||||||+                +|||||||||+  |",
"        -0.6 +          +|||||||+                  +|||||||+   |",
"        -0.8 +           ++||||+                    ++||||+    |",
"          -1 +---+--------+--------+-------+--------+--------+-+",
"                -4       -2        0       2        4        6  "
END_HELP(dumb)
#endif /* TERM_HELP */
